﻿namespace Extensions.Linq
{
    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Reflection;

    public class ComponentOperator
    {
        public TDelegate ConvertToExtensionMethod<TObject, TDelegate>(string methodName, Type[] types, BindingFlags bindingAttr)
        {
            types = (types == null) ? new Type[0] : types;
            ParameterExpression instance = Expression.Parameter(typeof(TObject));
            ParameterExpression[] arguments = new ParameterExpression[types.Length];
            for (int i = 0; i < types.Length; i++)
            {
                arguments[i] = Expression.Parameter(types[i]);
            }
            MethodCallExpression body = Expression.Call(instance, typeof(TObject).GetMethod(methodName, bindingAttr, null, types, null), arguments);
            List<ParameterExpression> list = new List<ParameterExpression>(arguments);
            list.Insert(0, instance);
            return Expression.Lambda<TDelegate>(body, list.ToArray()).Compile();
        }

        public TValue GetField<TObject, TValue>(TObject obj, Getter<TObject, TValue> getter)
        {
            return getter(obj);
        }

        public TValue GetField<TObject, TValue>(TObject obj, string fieldName)
        {
            return this.GetFieldGetter<TObject, TValue>(fieldName)(obj);
        }

        public Getter<TObject, TValue> GetFieldGetter<TObject, TValue>(string fieldName)
        {
            ParameterExpression expression = Expression.Parameter(typeof(TObject));
            return Expression.Lambda<Getter<TObject, TValue>>(Expression.Field(expression, fieldName), new ParameterExpression[] { expression }).Compile();
        }

        public Setter<TObject, TValue> GetFieldSetter<TObject, TValue>(string fieldName)
        {
            ParameterExpression expression = Expression.Parameter(typeof(TObject));
            ParameterExpression right = Expression.Parameter(typeof(TValue));
            return Expression.Lambda<Setter<TObject, TValue>>(Expression.Assign(Expression.Field(expression, fieldName), right), new ParameterExpression[] { expression, right }).Compile();
        }

        public TValue GetProperty<TObject, TValue>(TObject obj, Getter<TObject, TValue> getter)
        {
            return getter(obj);
        }

        public TValue GetProperty<TObject, TValue>(TObject obj, string propertyName)
        {
            return this.GetPropertyGetter<TObject, TValue>(propertyName)(obj);
        }

        public Getter<TObject, TValue> GetPropertyGetter<TObject, TValue>(string propertyName)
        {
            ParameterExpression expression = Expression.Parameter(typeof(TObject));
            return Expression.Lambda<Getter<TObject, TValue>>(Expression.Property(expression, propertyName), new ParameterExpression[] { expression }).Compile();
        }

        public Setter<TObject, TValue> GetPropertySetter<TObject, TValue>(string propertyName)
        {
            ParameterExpression expression = Expression.Parameter(typeof(TObject));
            ParameterExpression right = Expression.Parameter(typeof(TValue));
            return Expression.Lambda<Setter<TObject, TValue>>(Expression.Assign(Expression.Property(expression, propertyName), right), new ParameterExpression[] { expression, right }).Compile();
        }

        public void SetField<TObject, TValue>(TObject obj, Setter<TObject, TValue> setter, TValue value)
        {
            setter(obj, value);
        }

        public void SetField<TObject, TValue>(TObject obj, string fieldName, TValue value)
        {
            this.GetFieldSetter<TObject, TValue>(fieldName)(obj, value);
        }

        public void SetProperty<TObject, TValue>(TObject obj, Setter<TObject, TValue> setter, TValue value)
        {
            setter(obj, value);
        }

        public void SetProperty<TObject, TValue>(TObject obj, string propertyName, TValue value)
        {
            this.GetPropertySetter<TObject, TValue>(propertyName)(obj, value);
        }
    }
}

